/**
 * @file
 * SNMP table support implementation.
 */

/*
 * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 * Author: Martin Hentschel <info@cl-soft.de>
 *
 */

#include "lwip/apps/snmp_opts.h"

#if LWIP_SNMP /* don't build if not configured for use in lwipopts.h */

#include "lwip/apps/snmp_core.h"
#include "lwip/apps/snmp_table.h"
#include <string.h>

snmp_err_t snmp_table_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
{
    snmp_err_t ret = SNMP_ERR_NOSUCHINSTANCE;
    const struct snmp_table_node *table_node = (const struct snmp_table_node *)(const void *)instance->node;

    LWIP_UNUSED_ARG(root_oid);
    LWIP_UNUSED_ARG(root_oid_len);

    /* check min. length (fixed row entry definition, column, row instance oid with at least one entry */
    /* fixed row entry always has oid 1 */
    if ((instance->instance_oid.len >= 3) && (instance->instance_oid.id[0] == 1))
    {
        /* search column */
        const struct snmp_table_col_def *col_def = table_node->columns;
        u16_t i = table_node->column_count;
        while (i > 0)
        {
            if (col_def->index == instance->instance_oid.id[1])
            {
                break;
            }

            col_def++;
            i--;
        }

        if (i > 0)
        {
            /* everything may be overwritten by get_cell_instance_method() in order to implement special handling for single columns/cells */
            instance->asn1_type = col_def->asn1_type;
            instance->access    = col_def->access;
            instance->get_value = table_node->get_value;
            instance->set_test  = table_node->set_test;
            instance->set_value = table_node->set_value;

            ret = table_node->get_cell_instance(
                      &(instance->instance_oid.id[1]),
                      &(instance->instance_oid.id[2]),
                      instance->instance_oid.len - 2,
                      instance);
        }
    }

    return ret;
}

snmp_err_t snmp_table_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
{
    const struct snmp_table_node *table_node = (const struct snmp_table_node *)(const void *)instance->node;
    const struct snmp_table_col_def *col_def;
    struct snmp_obj_id row_oid;
    u32_t column = 0;
    snmp_err_t result;

    LWIP_UNUSED_ARG(root_oid);
    LWIP_UNUSED_ARG(root_oid_len);

    /* check that first part of id is 0 or 1, referencing fixed row entry */
    if ((instance->instance_oid.len > 0) && (instance->instance_oid.id[0] > 1))
    {
        return SNMP_ERR_NOSUCHINSTANCE;
    }
    if (instance->instance_oid.len > 1)
    {
        column = instance->instance_oid.id[1];
    }
    if (instance->instance_oid.len > 2)
    {
        snmp_oid_assign(&row_oid, &(instance->instance_oid.id[2]), instance->instance_oid.len - 2);
    }
    else
    {
        row_oid.len = 0;
    }

    instance->get_value    = table_node->get_value;
    instance->set_test     = table_node->set_test;
    instance->set_value    = table_node->set_value;

    /* resolve column and value */
    do
    {
        u16_t i;
        const struct snmp_table_col_def *next_col_def = NULL;
        col_def = table_node->columns;

        for (i = 0; i < table_node->column_count; i++)
        {
            if (col_def->index == column)
            {
                next_col_def = col_def;
                break;
            }
            else if ((col_def->index > column) && ((next_col_def == NULL) || (col_def->index < next_col_def->index)))
            {
                next_col_def = col_def;
            }
            col_def++;
        }

        if (next_col_def == NULL)
        {
            /* no further column found */
            return SNMP_ERR_NOSUCHINSTANCE;
        }

        instance->asn1_type          = next_col_def->asn1_type;
        instance->access             = next_col_def->access;

        result = table_node->get_next_cell_instance(
                     &next_col_def->index,
                     &row_oid,
                     instance);

        if (result == SNMP_ERR_NOERROR)
        {
            col_def = next_col_def;
            break;
        }

        row_oid.len = 0; /* reset row_oid because we switch to next column and start with the first entry there */
        column = next_col_def->index + 1;
    }
    while (1);

    /* build resulting oid */
    instance->instance_oid.len   = 2;
    instance->instance_oid.id[0] = 1;
    instance->instance_oid.id[1] = col_def->index;
    snmp_oid_append(&instance->instance_oid, row_oid.id, row_oid.len);

    return SNMP_ERR_NOERROR;
}


snmp_err_t snmp_table_simple_get_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
{
    snmp_err_t ret = SNMP_ERR_NOSUCHINSTANCE;
    const struct snmp_table_simple_node *table_node = (const struct snmp_table_simple_node *)(const void *)instance->node;

    LWIP_UNUSED_ARG(root_oid);
    LWIP_UNUSED_ARG(root_oid_len);

    /* check min. length (fixed row entry definition, column, row instance oid with at least one entry */
    /* fixed row entry always has oid 1 */
    if ((instance->instance_oid.len >= 3) && (instance->instance_oid.id[0] == 1))
    {
        ret = table_node->get_cell_value(
                  &(instance->instance_oid.id[1]),
                  &(instance->instance_oid.id[2]),
                  instance->instance_oid.len - 2,
                  &instance->reference,
                  &instance->reference_len);

        if (ret == SNMP_ERR_NOERROR)
        {
            /* search column */
            const struct snmp_table_simple_col_def *col_def = table_node->columns;
            u32_t i = table_node->column_count;
            while (i > 0)
            {
                if (col_def->index == instance->instance_oid.id[1])
                {
                    break;
                }

                col_def++;
                i--;
            }

            if (i > 0)
            {
                instance->asn1_type = col_def->asn1_type;
                instance->access    = SNMP_NODE_INSTANCE_READ_ONLY;
                instance->set_test  = NULL;
                instance->set_value = NULL;

                switch (col_def->data_type)
                {
                case SNMP_VARIANT_VALUE_TYPE_U32:
                    instance->get_value = snmp_table_extract_value_from_u32ref;
                    break;
                case SNMP_VARIANT_VALUE_TYPE_S32:
                    instance->get_value = snmp_table_extract_value_from_s32ref;
                    break;
                case SNMP_VARIANT_VALUE_TYPE_PTR: /* fall through */
                case SNMP_VARIANT_VALUE_TYPE_CONST_PTR:
                    instance->get_value = snmp_table_extract_value_from_refconstptr;
                    break;
                default:
                    LWIP_DEBUGF(SNMP_DEBUG, ("snmp_table_simple_get_instance(): unknown column data_type: %d\n", col_def->data_type));
                    return SNMP_ERR_GENERROR;
                }

                ret = SNMP_ERR_NOERROR;
            }
            else
            {
                ret = SNMP_ERR_NOSUCHINSTANCE;
            }
        }
    }

    return ret;
}

snmp_err_t snmp_table_simple_get_next_instance(const u32_t *root_oid, u8_t root_oid_len, struct snmp_node_instance *instance)
{
    const struct snmp_table_simple_node *table_node = (const struct snmp_table_simple_node *)(const void *)instance->node;
    const struct snmp_table_simple_col_def *col_def;
    struct snmp_obj_id row_oid;
    u32_t column = 0;
    snmp_err_t result;

    LWIP_UNUSED_ARG(root_oid);
    LWIP_UNUSED_ARG(root_oid_len);

    /* check that first part of id is 0 or 1, referencing fixed row entry */
    if ((instance->instance_oid.len > 0) && (instance->instance_oid.id[0] > 1))
    {
        return SNMP_ERR_NOSUCHINSTANCE;
    }
    if (instance->instance_oid.len > 1)
    {
        column = instance->instance_oid.id[1];
    }
    if (instance->instance_oid.len > 2)
    {
        snmp_oid_assign(&row_oid, &(instance->instance_oid.id[2]), instance->instance_oid.len - 2);
    }
    else
    {
        row_oid.len = 0;
    }

    /* resolve column and value */
    do
    {
        u32_t i;
        const struct snmp_table_simple_col_def *next_col_def = NULL;
        col_def = table_node->columns;

        for (i = 0; i < table_node->column_count; i++)
        {
            if (col_def->index == column)
            {
                next_col_def = col_def;
                break;
            }
            else if ((col_def->index > column) && ((next_col_def == NULL) ||
                                                   (col_def->index < next_col_def->index)))
            {
                next_col_def = col_def;
            }
            col_def++;
        }

        if (next_col_def == NULL)
        {
            /* no further column found */
            return SNMP_ERR_NOSUCHINSTANCE;
        }

        result = table_node->get_next_cell_instance_and_value(
                     &next_col_def->index,
                     &row_oid,
                     &instance->reference,
                     &instance->reference_len);

        if (result == SNMP_ERR_NOERROR)
        {
            col_def = next_col_def;
            break;
        }

        row_oid.len = 0; /* reset row_oid because we switch to next column and start with the first entry there */
        column = next_col_def->index + 1;
    }
    while (1);

    instance->asn1_type = col_def->asn1_type;
    instance->access    = SNMP_NODE_INSTANCE_READ_ONLY;
    instance->set_test  = NULL;
    instance->set_value = NULL;

    switch (col_def->data_type)
    {
    case SNMP_VARIANT_VALUE_TYPE_U32:
        instance->get_value = snmp_table_extract_value_from_u32ref;
        break;
    case SNMP_VARIANT_VALUE_TYPE_S32:
        instance->get_value = snmp_table_extract_value_from_s32ref;
        break;
    case SNMP_VARIANT_VALUE_TYPE_PTR: /* fall through */
    case SNMP_VARIANT_VALUE_TYPE_CONST_PTR:
        instance->get_value = snmp_table_extract_value_from_refconstptr;
        break;
    default:
        LWIP_DEBUGF(SNMP_DEBUG, ("snmp_table_simple_get_instance(): unknown column data_type: %d\n", col_def->data_type));
        return SNMP_ERR_GENERROR;
    }

    /* build resulting oid */
    instance->instance_oid.len   = 2;
    instance->instance_oid.id[0] = 1;
    instance->instance_oid.id[1] = col_def->index;
    snmp_oid_append(&instance->instance_oid, row_oid.id, row_oid.len);

    return SNMP_ERR_NOERROR;
}


s16_t
snmp_table_extract_value_from_s32ref(struct snmp_node_instance *instance, void *value)
{
    s32_t *dst = (s32_t *)value;
    *dst = instance->reference.s32;
    return sizeof(*dst);
}

s16_t
snmp_table_extract_value_from_u32ref(struct snmp_node_instance *instance, void *value)
{
    u32_t *dst = (u32_t *)value;
    *dst = instance->reference.u32;
    return sizeof(*dst);
}

s16_t
snmp_table_extract_value_from_refconstptr(struct snmp_node_instance *instance, void *value)
{
    MEMCPY(value, instance->reference.const_ptr, instance->reference_len);
    return (u16_t)instance->reference_len;
}

#endif /* LWIP_SNMP */
